﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;

namespace TTApp.Phone.Silverlight.Hexagons
{
    /// <summary>
    /// Dùng để lưu trạng thái của Storyboard, vì không thể lấy chính xác trạng thái của nó
    /// (chạy, tạm dừng hay đã dừng..).
    /// </summary>
    public enum StoryboardState
    {
        /// <summary>
        /// Trạng thái của Storyboard chưa được thiết lập.
        /// </summary>
        Unset, 
        /// <summary>
        /// Storyboard đang ở trạng thái chạy.
        /// </summary>
        Running,
        /// <summary>
        /// Storyboard đã được tạm dừng.
        /// </summary>
        Paused, 
        /// <summary>
        /// Storyboard đã dừng.
        /// </summary>
        Stopped
    }

    /// <summary>
    /// Hỗ trợ việc kéo (drag), vuốt (flick) và phóng (zoom) đối tượng nằm trong view của nó.
    /// </summary>
    [ContentProperty("Child")]
    public partial class GestureViewer : UserControl
    {
        #region //----------------------------- Class Private Fields --------------------------------------//
        /// <summary>
        /// Nội dung nằm trong viewbox
        /// </summary>
        private FrameworkElement _child;

        /// <summary>
        /// Transform áp dụng cho nội dung của viewbox
        /// </summary>
        private CompositeTransform _transform;

        /// <summary>
        /// Hình chữ nhật chứa kích thước nhỏ nhất của image
        /// </summary>
        private Rect _innerBound;

        /// <summary>
        /// Kích thước của image nhìn thấy, tức là phần nằm trong viewbox
        /// </summary>
        private Size _childSize;

        /// <summary>
        /// Tỉ lệ của _imageSize so với kích thước vật lý của nó
        /// </summary>
        private Point _childSizeRate;

        /// <summary>
        /// Độ scale của image khi bắt đầu pinch
        /// </summary>
        private double _initialScale;

        /// <summary>
        /// Tỉ lệ scale nhỏ nhất có thể của image. Được tính dựa vào _innerBound
        /// </summary>
        private double _minScale;

        /// <summary>
        /// Tỉ lệ scale lớn nhất
        /// </summary>
        private double _maxScale;

        /// <summary>
        /// Tỉ lệ scale thích hợp nhỏ nhất. Nếu image đang ở tỉ lệ nhỏ hơn tỉ lệ này, nó sẽ
        /// tự động được đưa về tỉ lệ này
        /// </summary>
        private double _preferredMinScale;

        /// <summary>
        /// Tương tự như _preferredMinScale
        /// </summary>
        private double _preferredMaxScale;

        /// <summary>
        /// Tâm 2 ngón tay trong 1 lần PinchDelta
        /// </summary>
        private Point _pinchCenter;

        /// <summary>
        /// Tỉ lệ để tính _innerBound
        /// </summary>
        private double _boundRate = 0.3;

        /// <summary>
        /// Thay đổi độ lớn của vận tốc vuốt
        /// </summary>
        private double _flickVelocityScale = 0.45;

        /// <summary>
        /// Được bật nếu quá trình drag bắt đầu khi không có storyboard nào đang chạy
        /// </summary>
        [Obsolete("Không cần kiểm tra nữa vì có thể dừng Storyboard bất kì lúc nào")]
        private bool _isDragEnabled;

        /// <summary>
        /// Được bật nếu quá trình pinch bắt đầu khi không có storyboard nào đang chạy
        /// </summary>
        [Obsolete("Không cần kiểm tra nữa vì có thể dừng Storyboard bất kì lúc nào")]
        private bool _isPinchEnable;

        /// <summary>
        /// Trạng thái của restoreTranslateStoryboard
        /// </summary>
        StoryboardState _restoreTranslateStoryboardState = StoryboardState.Unset;

        /// <summary>
        /// Trạng thái của restoreScaleStoryboardState
        /// </summary>
        StoryboardState _restoreScaleStoryboardState = StoryboardState.Unset;

        /// <summary>
        /// Trạng thái của flickStoryboard
        /// </summary>
        StoryboardState _flickStoryboardState = StoryboardState.Unset;

        /// <summary>
        /// Thời gian chạy mặc định cho mọi storyboard.
        /// </summary>
        private Duration _defaultDuration = new Duration(TimeSpan.FromSeconds(0.45));

        /// <summary>
        /// Thời gian cho trôi của image sau khi được flick
        /// </summary>
        private Duration _flickDuration = new Duration(TimeSpan.FromSeconds(0.75));

        /// <summary>
        /// Cờ đánh dấu _preferredMaxScale được set bởi người dùng hay không.
        /// </summary>
        private bool _isUserSetPreferredMaxScale = false;

        /// <summary>
        /// Cờ đánh dấu _preferredMinScale được set bởi người dùng hay không.
        /// </summary>
        private bool _isUserSetPreferredMinScale = false;

        /// <summary>
        /// Cờ đánh dấu _maxScale được set bởi người dùng hay không.
        /// </summary>
        private bool _isUserSetMaxScale = false;

        /// <summary>
        /// Cờ để xác định các giá trị đã được khởi tạo qua hàm InitializeVariables() hay chưa.
        /// Các thuộc tính được khởi tạo hoàn toàn khi OnLoaded được gọi.
        /// </summary>
        private bool _isInitialized = false;
        #endregion

        #region //----------------------------- Class Public Constructors ---------------------------------//
        /// <summary>
        /// Khởi tạo 1 đối tượng GestureViewer mới.
        /// </summary>
        public GestureViewer()
        {
            InitializeComponent();
            Loaded += new RoutedEventHandler(OnLoaded);
            SizeChanged += new SizeChangedEventHandler(OnSizeChanged);
        }
        #endregion

        #region //----------------------------- Class CLR Properties --------------------------------------//
        /// <summary>
        /// Nội dung chính của GestureViewer. Property này ẩn property Content của UserControl. Setter 
        /// được chuyển sang protected.
        /// </summary>
        public new UIElement Content
        {
            get
            {
                return base.Content;
            }
            protected set
            {
                base.Content = value;
            }
        }

        /// <summary>
        /// Lấy hoặc gán phần nội dung nằm trong viewbox, nghĩa là phần nhìn thấy khi thao tác kéo, vuốt...
        /// </summary>
        public UIElement Child
        {
            get
            {
                return viewbox.Child;
            }
            set
            {
                // Hủy đăng kí sự kiện cho nội dung của view box nếu cần
                if (viewbox.Child != null)
                {
                    GestureListener gl = GestureService.GetGestureListener(viewbox.Child);
                    gl.Tap -= OnTap;
                    //gl.DoubleTap -= OnDoubleTap;
                    gl.Flick -= OnFlick;
                    gl.DragStarted -= OnDragStarted;
                    gl.DragDelta -= OnDragDelta;
                    gl.DragCompleted -= OnDragCompleted;
                    gl.GestureBegin -= OnGestureBegin;
                    gl.GestureCompleted -= OnGestureCompleted;
                    gl.PinchStarted -= OnPinchStarted;
                    gl.PinchDelta -= OnPinchDelta;
                    gl.PinchCompleted -= OnPinchCompleted;

                    viewbox.Child.DoubleTap -= OnChildDoubleTap;
                }
                viewbox.Child = value;
                _child = viewbox.Child as FrameworkElement;

                // Gán lại các giá trị khi kích thước child thay đổi
                _child.SizeChanged += OnChildSizeChanged;

                // Cache để khi thao tác không bị giật
                _child.CacheMode = new BitmapCache();

                // Transform
                _transform = new CompositeTransform();
                _child.RenderTransform = _transform;

                // Storyboard
                Storyboard.SetTarget(restoreTranslateXAnimation, _transform);
                Storyboard.SetTarget(restoreTranslateYAnimation, _transform);
                Storyboard.SetTarget(restoreScaleXAnimation, _transform);
                Storyboard.SetTarget(restoreScaleYAnimation, _transform);
                Storyboard.SetTarget(flickXAnimation, _transform);
                Storyboard.SetTarget(flickYAnimation, _transform);

                // GestureListener
                GestureListener gestureListener = GestureService.GetGestureListener(_child);
                gestureListener.Tap += OnTap;
                //gestureListener.DoubleTap += OnDoubleTap;
                gestureListener.Flick += OnFlick;
                gestureListener.DragStarted += OnDragStarted;
                gestureListener.DragDelta += OnDragDelta;
                gestureListener.DragCompleted += OnDragCompleted;
                gestureListener.GestureBegin += OnGestureBegin;
                gestureListener.GestureCompleted += OnGestureCompleted;
                gestureListener.PinchStarted += OnPinchStarted;
                gestureListener.PinchDelta += OnPinchDelta;
                gestureListener.PinchCompleted += OnPinchCompleted;

                _child.DoubleTap += new EventHandler<System.Windows.Input.GestureEventArgs>(OnChildDoubleTap);

                // Thiết lập các thông số mới
                InitializeVariables();
            }
        }

        /// <summary>
        /// Lấy giá trị nhỏ nhất có thể scale được. Giá trị này được tính dựa theo kích thước của 
        /// image, viewbox và root. Không gán được.
        /// </summary>
        public double MinScale
        {
            get { return _minScale; }
        }

        /// <summary>
        /// Lấy hoặc gán giá trị scale lớn nhất có thể.
        /// </summary>
        public double MaxScale
        {
            get
            {
                return _maxScale;
            }
            set
            {
                AssertScaleValue();

                _maxScale = value;
                _isUserSetMaxScale = true;
            }
        }

        /// <summary>
        /// Độ scale thích hợp mức nhỏ. Nếu image đang ở mức nhỏ hơn thì tự động được đưa về mức này
        /// </summary>
        public double PrefferedMinScale
        {
            get
            {
                return _preferredMinScale;
            }
            set
            {
                AssertScaleValue();

                _preferredMinScale = value;
                _isUserSetPreferredMinScale = true;
            }
        }

        /// <summary>
        /// Độ scale thích hợp mức lớn. Nếu image đang ở mức lớn hơn thì tự động được đưa về mức này
        /// </summary>
        public double PreferredMaxScale
        {
            get
            {
                return _preferredMaxScale;
            }
            set
            {
                AssertScaleValue();

                _preferredMaxScale = value;
                _isUserSetPreferredMaxScale = true;
            }
        }

        /// <summary>
        /// Lấy hoặc gán độ dãn của ảnh. Kiểu None không hỗ trợ.
        /// </summary>
        public Stretch Stretch
        {
            get
            {
                return viewbox.Stretch;
            }
            set
            {
                if (value == Stretch.None)
                {
                    throw new NotSupportedException("Không hỗ trợ None. Chọn Fill, Uniform hoặc UniformToFill.");
                }
                else
                {
                    viewbox.Stretch = value;
                }
            }

        }

        /// <summary>
        /// Tỉ lệ vượt biên của nội dung khi chạm cạnh
        /// </summary>
        public double BoundRate
        {
            get
            {
                return _boundRate;
            }
            set
            {
                if (value < 0 || 0.5 <= value)
                {
                    throw new ArgumentOutOfRangeException("value", "Must be in [0-0.5) range");
                }
                _boundRate = value;
                InitializeVariables();
            }
        }

        /// <summary>
        /// Lấy mức độ scale hiện tại của Child.
        /// </summary>
        public double CurrentScale
        {
            get { return _transform.ScaleX; }
        }
        #endregion

        #region //----------------------------- Private Event Handlers ------------------------------------//
        private void OnTap(object sender, Microsoft.Phone.Controls.GestureEventArgs args)
        {
        }

        private void OnDoubleTap(object sender, Microsoft.Phone.Controls.GestureEventArgs args)
        {
            _pinchCenter = args.GetPosition(_child);
            _pinchCenter.X *= _transform.ScaleX;
            _pinchCenter.Y *= _transform.ScaleY;

            // Sai số
            if (Math.Abs(_transform.ScaleX - _preferredMaxScale) < 0.1)
            {
                SetBackEaseAmplitude(0.35);
                RestoreScale(_preferredMinScale);
            }
            else
            {
                SetBackEaseAmplitude(1.0);
                RestoreScale(_preferredMaxScale);
            }
        }

        private void OnChildDoubleTap(object sender, System.Windows.Input.GestureEventArgs args)
        {
            // Event handler của _child.
            _pinchCenter = args.GetPosition(_child);
            _pinchCenter.X *= _transform.ScaleX;
            _pinchCenter.Y *= _transform.ScaleY;

            // Sai số
            if (Math.Abs(_transform.ScaleX - _preferredMaxScale) < 0.1)
            {
                SetBackEaseAmplitude(0.35);
                RestoreScale(_preferredMinScale);
            }
            else
            {
                SetBackEaseAmplitude(1.0);
                RestoreScale(_preferredMaxScale);
            }
        }

        private void OnFlick(object sender, FlickGestureEventArgs args)
        {
            if (IsFlickValid(new Point(args.HorizontalVelocity, args.VerticalVelocity)))
            {
                // Lượng dịch chuyển theo các trục
                double flickXTranslate = args.HorizontalVelocity * _flickVelocityScale * _flickDuration.TimeSpan.TotalSeconds;
                double flickYTranslate = args.VerticalVelocity * _flickVelocityScale * _flickDuration.TimeSpan.TotalSeconds;

                UpdateFlick(new Point(flickXTranslate, flickYTranslate));
            }
        }

        private void OnDragStarted(object sender, DragStartedGestureEventArgs args)
        {
        }

        private void OnDragDelta(object sender, DragDeltaGestureEventArgs args)
        {
            Point transitionDelta = new Point(
            args.HorizontalChange, args.VerticalChange);

            if (IsDragValid(transitionDelta))
            {
                UpdateImagePosition(transitionDelta);
            }
        }

        private void OnDragCompleted(object sender, DragCompletedGestureEventArgs args)
        {
            // Không có flick thì kiểm tra vị trí
            if (args.HorizontalVelocity == 0 && args.VerticalVelocity == 0)
            {
                CheckPosition();
            }
        }

        private void OnGestureBegin(object sender, Microsoft.Phone.Controls.GestureEventArgs args)
        {

        }

        private void OnGestureCompleted(object sender, Microsoft.Phone.Controls.GestureEventArgs args)
        {
            // Nếu không có storyboard nào đang chạy thì kiểm tra vị trí
            if (_restoreScaleStoryboardState != StoryboardState.Running &&
                _restoreTranslateStoryboardState != StoryboardState.Running &&
                _flickStoryboardState != StoryboardState.Running)
            {
                CheckPosition();
            }
        }

        private void OnPinchStarted(object sender, PinchStartedGestureEventArgs args)
        {
            _initialScale = _transform.ScaleX;

            Point finger1 = args.GetPosition(_child, 0);
            Point finger2 = args.GetPosition(_child, 1);

            _pinchCenter = new Point((finger1.X + finger2.X) / 2, (finger1.Y + finger2.Y) / 2);
            _pinchCenter.X *= _transform.ScaleX;
            _pinchCenter.Y *= _transform.ScaleY;
        }

        private void OnPinchDelta(object sender, PinchGestureEventArgs args)
        {
            double currentScale = _initialScale * args.DistanceRatio;
            double validScale = GetValidScale(currentScale);

            Point transitionDelta = CalculateTransition(validScale);

            UpdateImageScale(validScale);
            UpdateImagePosition(transitionDelta);
        }

        private void OnPinchCompleted(object sender, PinchGestureEventArgs args)
        {
            CheckScale();
        }

        private void OnRestoreScaleStoryboardCompleted(object sender, EventArgs args)
        {
            _restoreScaleStoryboardState = StoryboardState.Stopped;
        }

        private void OnRestoreTranslateStoryboardCompleted(object sender, EventArgs args)
        {
            _restoreTranslateStoryboardState = StoryboardState.Stopped;
        }

        private void OnFlickStoryboardCompleted(object sender, EventArgs args)
        {
            _flickStoryboardState = StoryboardState.Stopped;
            CheckPosition();
        }

        private void OnLoaded(object sender, RoutedEventArgs args)
        {
            _isInitialized = true;

            InitializeVariables();
        }

        private void OnSizeChanged(object sender, SizeChangedEventArgs args)
        {
            if (_transform != null)
            {
                _transform.TranslateX = _transform.TranslateY = 0;
                _transform.ScaleX = _transform.ScaleY = 1;
            }

            InitializeVariables();
        }


        private void OnChildSizeChanged(object sender, SizeChangedEventArgs args)
        {
            InitializeVariables();
        }
        #endregion

        #region //----------------------------- Overridden Methods ----------------------------------------//
        /// <summary>
        /// Ngừng nội dung trôi nếu nó vừa được flick.
        /// </summary>
        /// <param name="args"></param>
        protected override void OnManipulationStarted(ManipulationStartedEventArgs args)
        {
            PauseAllStoryboard();

            args.Handled = true;
            args.Complete();

            base.OnManipulationStarted(args);
        }
        #endregion

        #region //----------------------------- Class Private Methods -------------------------------------//
        #region //-------------- Checking ---------------//
        /// <summary>
        /// Kiểm tra xem có kéo được image không. 
        /// </summary>
        /// <param name="transitionDelta">Dùng để xác định hướng chuyển động, không dùng độ lớn</param>
        /// <returns>true nếu 1 trong 2 phương x, y của image có thể chuyển động được</returns>
        private bool IsDragValid(Point transitionDelta)
        {
            bool canMoveX = true, canMoveY = true;

            // Vị trí hiện tại của image
            Rect imagePosition = GetImagePosition();

            if (transitionDelta.X > 0 && imagePosition.Left >= _innerBound.Left)
            {
                canMoveX = false;
            }
            else if (transitionDelta.X < 0 && imagePosition.Right <= _innerBound.Right)
            {
                canMoveX = false;
            }

            if (transitionDelta.Y > 0 && imagePosition.Top >= _innerBound.Top)
            {
                canMoveY = false;
            }
            else if (transitionDelta.Y < 0 && imagePosition.Bottom <= _innerBound.Bottom)
            {
                canMoveY = false;
            }

            return (canMoveX || canMoveY);
        }

        /// <summary>
        /// Kiểm tra xem có thể thao tác được không.
        /// </summary>
        /// <returns>false nếu có 1 storyboard nào đó đang chạy</returns>
        [Obsolete("Không cần dùng vì dừng Storyboard ở bất kì thời điểm nào cũng không sao")]
        private bool IsGestureEnable()
        {
            return (_restoreTranslateStoryboardState != StoryboardState.Running &&
                    _restoreScaleStoryboardState != StoryboardState.Running);
        }

        /// <summary>
        /// Kiểm tra xem có scale được image hay không
        /// </summary>
        /// <param name="scale">Giá trị scale mới</param>
        /// <returns></returns>
        private bool IsScaleValid(double scale)
        {
            return (_minScale <= scale || scale <= _maxScale);
        }

        /// <summary>
        /// Kiểm tra xem có flick được hay không.
        /// </summary>
        /// <param name="velocity">Vận tốc của flick</param>
        /// <returns>false nếu không chuyển động được theo trục nào</returns>
        private bool IsFlickValid(Point velocity)
        {
            bool canFlickX = true, canFlickY = true;
            Rect imagePosition = GetImagePosition();

            // Đứng im theo trục X
            if (velocity.X == 0)
            {
                canFlickX = false;
            }
            // Sang phải theo trục X
            if (velocity.X > 0 && imagePosition.Left >= _innerBound.Left)
            {
                canFlickX = false;
            }
            // Sang trái theo trục X
            else if (velocity.X < 0 && imagePosition.Right <= _innerBound.Right)
            {
                canFlickX = false;
            }

            // Đứng im theo trục Y
            if (velocity.Y == 0)
            {
                canFlickY = false;
            }
            else if (velocity.Y > 0 && imagePosition.Top >= _innerBound.Top)
            {
                canFlickY = false;
            }
            else if (velocity.Y < 0 && imagePosition.Bottom <= _innerBound.Bottom)
            {
                canFlickY = false;
            }

            return (canFlickX || canFlickY);
        }
        #endregion

        #region //-------------- Position ---------------//
        /// <summary>
        /// Cập nhật vị trí của image.
        /// </summary>
        /// <param name="transitionDelta">Lượng dịch chuyển có thể sẽ áp dụng</param>
        private void UpdateImagePosition(Point transitionDelta)
        {
            // Vị trí của ảnh trước khi dịch
            Rect imagePosition = new Rect(
                _transform.TranslateX / _childSizeRate.X,
                _transform.TranslateY / _childSizeRate.Y,
                _childSize.Width * _transform.ScaleX,
                _childSize.Height * _transform.ScaleY);

            // Vị trí mới của ảnh sau khi được dịch
            Rect newPosition = new Rect(
                imagePosition.Left + transitionDelta.X,
                imagePosition.Top + transitionDelta.Y,
                imagePosition.Width,
                imagePosition.Height);

            // Biến lưu giá trị dịch cuối cùng
            Point newTransition = new Point(transitionDelta.X, transitionDelta.Y);

            if (newPosition.Left >= _innerBound.Left)
            {
                newTransition.X = _innerBound.Left - imagePosition.Left;
            }
            else if (newPosition.Right <= _innerBound.Right)
            {
                newTransition.X = _innerBound.Right - imagePosition.Right;
            }

            if (newPosition.Top >= _innerBound.Top)
            {
                newTransition.Y = _innerBound.Top - imagePosition.Top;
            }
            else if (newPosition.Bottom <= _innerBound.Bottom)
            {
                newTransition.Y = _innerBound.Bottom - imagePosition.Bottom;
            }

            ApplyTransition(newTransition);
        }

        /// <summary>
        /// Áp dụng dịch chuyển cho image
        /// </summary>
        /// <param name="transition">Lượng dịch chuyển so với root.</param>
        private void ApplyTransition(Point transition)
        {
            _transform.TranslateX += (transition.X * _childSizeRate.X);
            _transform.TranslateY += (transition.Y * _childSizeRate.Y);
        }

        /// <summary>
        /// Đưa ảnh trở về trạng thái thích hợp sau khi zoom, drag hoặc flick
        /// </summary>
        private void CheckPosition()
        {
            // Vị trí hiện tại của image, tính theo tọa độ của viewbox
            Rect imagePosition = new Rect(
                _transform.TranslateX / _childSizeRate.X,
                _transform.TranslateY / _childSizeRate.Y,
                _childSize.Width * _transform.ScaleX,
                _childSize.Height * _transform.ScaleY);

            RestorePosition(imagePosition);
        }

        /// <summary>
        /// Kiểm tra vị trí hiện tại của image có bị vượt góc hay không. Nếu có thì điều chỉnh
        /// </summary>
        /// <param name="imagePosition">Vị trí hiện tại của image so với viewbox</param>
        private void RestorePosition(Rect imagePosition)
        {
            // Độ dịch của ảnh nếu cần thiết
            double translateX = 0, translateY = 0;

            // Chuyển sang tọa độ so với root, sẽ vẫn giữ nguyên nếu cạnh của viewbox >= cạnh của root
            Rect imageRootPosition = GetImageRootPosition();

            // Chiều rộng của ảnh nhỏ hơn root và viewbox cũng nhỏ hơn root thì căn vào giữa viewbox
            if (imagePosition.Width < root.ActualWidth && viewbox.ActualWidth < root.ActualWidth)
            {
                double viewboxCenter = viewbox.ActualWidth / 2;
                double imageCenter = imagePosition.Left + imagePosition.Width / 2;

                translateX = (viewboxCenter - imageCenter) * _childSizeRate.X;
            }
            // Còn không thì căn so với root, dùng tọa độ so với root
            else
            {
                if (imageRootPosition.Left >= 0)
                {
                    translateX = -(imageRootPosition.Left * _childSizeRate.X);
                }
                else if (imageRootPosition.Right <= root.ActualWidth)
                {
                    translateX = (root.ActualWidth - imageRootPosition.Right) * _childSizeRate.X;
                }
            }

            // Chiều cao ảnh nhỏ hơn root và chiều cao của viewbox cũng nhỏ hơn root thì căn vào giữa viewbox
            if (imagePosition.Height < root.ActualHeight && viewbox.ActualHeight < root.ActualHeight)
            {
                double viewboxCenter = viewbox.ActualHeight / 2;
                double imageCenter = imagePosition.Top + imagePosition.Height / 2;

                translateY = (viewboxCenter - imageCenter) * _childSizeRate.Y;
            }
            // Còn không thì căn so với root, dùng tọa độ so với root
            else
            {
                if (imageRootPosition.Top >= 0)
                {
                    translateY = -(imageRootPosition.Top * _childSizeRate.Y);
                }
                else if (imageRootPosition.Bottom <= root.ActualHeight)
                {
                    translateY = (root.ActualHeight - imageRootPosition.Bottom) * _childSizeRate.Y;
                }
            }

            // Dịch chuyển nếu cần thiết
            if (translateX != 0 || translateY != 0)
            {
                SetBackEaseAmplitude(0.35);
                TranslateAnimation(translateX, translateY, _defaultDuration);
            }
        }

        /// <summary>
        /// Dịch chuyển vị trí của image theo tham số
        /// </summary>
        /// <param name="byOnX">Độ dịch chuyển theo trục x</param>
        /// <param name="byOnY">Độ dịch chuyển theo trục y</param>
        /// <param name="duration">Thời gian dịch chuyển</param>
        private void TranslateAnimation(double byOnX, double byOnY, Duration duration)
        {
            if (byOnX != 0 || byOnY != 0)
            {
                restoreTranslateXAnimation.By = byOnX;
                restoreTranslateYAnimation.By = byOnY;
                restoreTranslateXAnimation.Duration = restoreTranslateYAnimation.Duration = duration;

                restoreTranslateStoryboard.Begin();
                _restoreTranslateStoryboardState = StoryboardState.Running;
            }
        }
        #endregion

        #region //-------------- Scale ------------------//
        /// <summary>
        /// Kiểm tra lượng scale, tính toán cho thỏa mãn rồi áp dụng
        /// </summary>
        /// <param name="newScale">Lượng scale có thể sẽ được áp dụng</param>
        private void UpdateImageScale(double newScale)
        {
            ApplyScale(newScale);
        }

        /// <summary>
        /// Áp dụng scale cho image
        /// </summary>
        /// <param name="newScale">Lượng scale mới của image</param>
        private void ApplyScale(double newScale)
        {
            _transform.ScaleX = _transform.ScaleY = newScale;
        }

        /// <summary>
        /// Kiểm tra độ scale hiện tại của image có nhỏ hơn _preferredMinScale hay có lớn hơn 
        /// _preferredMaxScale không. Có thì điều chỉnh.
        /// </summary>
        private void CheckScale()
        {
            double currentScale = _transform.ScaleX;

            if (currentScale < _preferredMinScale)
            {
                SetBackEaseAmplitude(0.35);
                RestoreScale(_preferredMinScale);
            }
            else if (currentScale > _preferredMaxScale)
            {
                SetBackEaseAmplitude(0.35);
                RestoreScale(_preferredMaxScale);
            }
            else
            {
                CheckPosition();
            }
        }

        /// <summary>
        /// Điều chỉnh độ scale của image đến newScale.
        /// </summary>
        /// <param name="newScale">Độ scale mới của image</param>
        private void RestoreScale(double newScale)
        {
            Point totalTranslateDelta = CalculateScaleTransition(newScale);

            TranslateAnimation(
                totalTranslateDelta.X * _childSizeRate.X,
                totalTranslateDelta.Y * _childSizeRate.Y,
                _defaultDuration);

            ScaleAnimation(newScale, _defaultDuration);
        }

        /// <summary>
        /// Điều chỉnh độ scale của ảnh với thời gian truyền vào.
        /// </summary>
        /// <param name="newScale">Độ scale mới.</param>
        /// <param name="duration">Thời gian diễn ra scale.</param>
        private void RestoreScale(double newScale, Duration duration)
        {
            Point totalTranslateDelta = CalculateScaleTransition(newScale);

            TranslateAnimation(
                totalTranslateDelta.X * _childSizeRate.X,
                totalTranslateDelta.Y * _childSizeRate.Y,
                duration);

            ScaleAnimation(newScale, duration);
        }

        /// <summary>
        /// Phóng image theo theo tham số
        /// </summary>
        /// <param name="newScale">Độ scale mới của image</param>
        /// <param name="duration">Thời gian phóng</param>
        private void ScaleAnimation(double newScale, Duration duration)
        {
            if (newScale != _transform.ScaleX)
            {
                restoreScaleXAnimation.To = restoreScaleYAnimation.To = newScale;
                restoreScaleXAnimation.Duration = restoreScaleYAnimation.Duration = duration;

                restoreScaleStoryboard.Begin();
                _restoreScaleStoryboardState = StoryboardState.Running;
            }
        }
        #endregion

        #region //-------------- Flick ------------------//
        /// <summary>
        /// Tính toán lượng khoảng cách image sẽ trôi
        /// </summary>
        /// <param name="transitionDelta">Khoảng cách có thể trôi tính theo root</param>
        private void UpdateFlick(Point transitionDelta)
        {
            // Tọa độ ảnh tính theo viewbox và kích thước tính theo root
            Rect imagePosition = GetImagePosition();

            // Tọa độ ảnh sau khi flick (nếu flick thỏa mãn)
            Rect newImagePosition = GetImagePosition(
                new Point(
                    _transform.TranslateX + transitionDelta.X * _childSizeRate.X,
                    _transform.TranslateY + transitionDelta.Y * _childSizeRate.Y),
                _transform.ScaleX);

            // Lượng khoảng cách thực sự mà image sẽ di chuyển
            double realXTranslate = transitionDelta.X;
            double realYTranslate = transitionDelta.Y;

            // Trục X
            if (newImagePosition.Left > _innerBound.Left)
            {
                realXTranslate -= (newImagePosition.Left - _innerBound.Left);
            }
            else if (newImagePosition.Right < _innerBound.Right)
            {
                realXTranslate += (_innerBound.Right - newImagePosition.Right);
            }

            // Trục Y
            if (newImagePosition.Top > _innerBound.Top)
            {
                realYTranslate -= (newImagePosition.Top - _innerBound.Top);
            }
            else if (newImagePosition.Bottom < _innerBound.Bottom)
            {
                realYTranslate += (_innerBound.Bottom - newImagePosition.Bottom);
            }

            // Tính thời gian theo khoảng cách thực sự di chuyển
            double timeOnX, timeOnY;

            //Luôn có 1 trục khác không, vì nếu 2 trục bằng không thì IsFlickValid trả về false
            if (transitionDelta.X == 0)
            {
                timeOnX = Double.MaxValue;
            }
            else
            {
                timeOnX = (realXTranslate / transitionDelta.X) * _flickDuration.TimeSpan.TotalSeconds;
            }

            if (transitionDelta.Y == 0)
            {
                timeOnY = Double.MaxValue;
            }
            else
            {
                timeOnY = (realYTranslate / transitionDelta.Y) * _flickDuration.TimeSpan.TotalSeconds;
            }

            double minTime = Math.Min(timeOnX, timeOnY);

            ApplyFlick(new Point(realXTranslate, realYTranslate), new Duration(TimeSpan.FromSeconds(minTime)));
        }

        /// <summary>
        /// Áp dụng flick animation cho image.
        /// </summary>
        /// <param name="transition">Lượng khoảng cách thực sự sẽ di chuyển tính theo root</param>
        /// <param name="duration">Khoảng thời gian di chuyển</param>
        private void ApplyFlick(Point transition, Duration duration)
        {
            FlickAnimation(transition.X * _childSizeRate.X, transition.Y * _childSizeRate.Y, duration);
        }

        /// <summary>
        /// Flick image theo tham số
        /// </summary>
        /// <param name="byOnX">Độ flick theo trục X tính theo viewbox</param>
        /// <param name="byOnY">Độ flick theo trục Y tính theo viewbox</param>
        /// <param name="duration">Khoảng thời gian diễn ra flick</param>
        private void FlickAnimation(double byOnX, double byOnY, Duration duration)
        {
            if (byOnX != 0 || byOnY != 0)
            {
                flickXAnimation.By = byOnX;
                flickYAnimation.By = byOnY;
                flickXAnimation.Duration = flickYAnimation.Duration = duration;

                flickStoryboard.Begin();
                _flickStoryboardState = StoryboardState.Running;
            }
        }
        #endregion

        #region //-------------- Getter -----------------//
        /// <summary>
        /// Tính độ dịch chuyển tâm khi scale
        /// </summary>
        /// <param name="newScale">Lượng scale mới</param>
        /// <returns>Lượng dịch chuyển cần để cho tâm mới trùng với tâm cũ trên màn hình</returns>
        private Point CalculateTransition(double newScale)
        {
            // Tọa độ tâm khi chưa scale
            Point originalPinchCenter = new Point(
                _pinchCenter.X / _transform.ScaleX,
                _pinchCenter.Y / _transform.ScaleY);

            // Tọa độ tâm cũ khi áp dụng scale mới
            Point scaledOriginalPinchCenter = new Point(
                originalPinchCenter.X * newScale,
                originalPinchCenter.Y * newScale);

            // Độ dịch tâm khi áp dụng scale mới
            Point transitionDelta = new Point(
                scaledOriginalPinchCenter.X - _pinchCenter.X,
                scaledOriginalPinchCenter.Y - _pinchCenter.Y);

            // Đổi sang vị trí tâm sau khi scale.
            _pinchCenter = scaledOriginalPinchCenter;

            // Chỉnh lại theo tọa độ của root. 
            return new Point(
                -transitionDelta.X / _childSizeRate.X,
                -transitionDelta.Y / _childSizeRate.Y);
        }

        /// <summary>
        /// Lấy giá trị scale thích hợp bằng cách so sánh với _minScale và _maxScale.
        /// </summary>
        /// <param name="scale">giá trị scale được kiểm tra</param>
        /// <returns>giá trị scale thích hợp</returns>
        private double GetValidScale(double scale)
        {
            if (scale < _minScale)
            {
                return _minScale;
            }
            else if (scale > _maxScale)
            {
                return _maxScale;
            }

            return scale;
        }

        /// <summary>
        /// Lấy vị trí của ảnh (tính theo viewbox) và kích thước của ảnh (tính theo root)
        /// </summary>
        /// <returns>Vị trí và kích thước ảnh</returns>
        private Rect GetImagePosition()
        {
            return new Rect(
                _transform.TranslateX / _childSizeRate.X,
                _transform.TranslateY / _childSizeRate.Y,
                _childSize.Width * _transform.ScaleX,
                _childSize.Height * _transform.ScaleY);
        }

        /// <summary>
        /// Lấy vị trí của ảnh (tính theo viewbox) và kích thước của ảnh (tính theo root)
        /// </summary>
        /// <param name="delta">Tổng lượng dịch chuyển</param>
        /// <param name="scale">Tổng lượng scale</param>
        /// <returns></returns>
        private Rect GetImagePosition(Point delta, double scale)
        {
            return new Rect(
                delta.X / _childSizeRate.X,
                delta.Y / _childSizeRate.Y,
                _childSize.Width * scale,
                _childSize.Height * scale);
        }

        /// <summary>
        /// Chuyển tọa độ của image từ viewbox sang root. Tọa độ root giống viewbox nếu viewbox > root.
        /// </summary>
        /// <returns></returns>
        private Rect GetImageRootPosition()
        {
            Rect imageRootPosition = GetImagePosition();

            if (viewbox.ActualWidth < root.ActualWidth)
            {
                imageRootPosition.X = (root.ActualWidth - viewbox.ActualWidth) / 2 + imageRootPosition.Left;
            }
            if (viewbox.ActualHeight < root.ActualHeight)
            {
                imageRootPosition.Y = (root.ActualHeight - viewbox.ActualHeight) / 2 + imageRootPosition.Top;
            }

            return imageRootPosition;
        }
        #endregion

        #region //-------------- Helpers ----------------//
        /// <summary>
        /// Thiết lập các biến cần thiết theo chiều dài và rộng của các đối tượng.
        /// </summary>
        private void InitializeVariables()
        {
            if (_child != null && _child.ActualWidth != 0 && _child.ActualHeight != 0)
            {
                // Khoảng cách dùng để tính biên
                double distance;
                
                if (viewbox.ActualHeight < root.ActualHeight || viewbox.ActualWidth < root.ActualWidth)
                {
                    distance = Math.Min(viewbox.ActualWidth * _boundRate, viewbox.ActualHeight * _boundRate);
                }
                else
                {
                    distance = Math.Min(root.ActualWidth * _boundRate, root.ActualHeight * _boundRate);
                }

                // Biên mặc định, tính theo root, sẽ được căn chỉnh nếu viewbox < root
                _innerBound = new Rect(
                    distance,
                    distance,
                    root.ActualWidth - 2 * distance,
                    root.ActualHeight - 2 * distance);

                if (viewbox.ActualWidth < root.ActualWidth)
                {
                    double diff = root.ActualWidth - viewbox.ActualWidth;
                    _innerBound.Width = root.ActualWidth - diff - 2 * distance;
                }

                if (viewbox.ActualHeight < root.ActualHeight)
                {
                    double diff = root.ActualHeight - viewbox.ActualHeight;
                    _innerBound.Height = root.ActualHeight - diff - 2 * distance;
                }

                // Kích thước thật của ảnh
                _childSize = new Size(
                    viewbox.ActualWidth,
                    viewbox.ActualHeight);

                // Tỉ lệ kích thước của ảnh so với viewbox
                _childSizeRate = new Point(
                    _child.ActualWidth / viewbox.ActualWidth,
                    _child.ActualHeight / viewbox.ActualHeight);

                // Độ scale của image tính theo chiều dài và rộng của viewbox
                // Lấy giá trị lớn hơn vì lấy giá trị nhỏ hơn thì 1 kích thước sẽ nhỏ hơn _innerBound
                _minScale = Math.Max(
                    _innerBound.Width / _childSize.Width,
                    _innerBound.Height / _childSize.Height);

                // Các giá trị mặc định cho các biến scale
                if (!_isUserSetMaxScale)
                {
                    _maxScale = 8.0 * _minScale;
                }
                if (!_isUserSetPreferredMaxScale)
                {
                    _preferredMaxScale = 0.6 * _maxScale;
                }
                if (!_isUserSetPreferredMinScale)
                {
                    _preferredMinScale = Math.Max(
                        viewbox.ActualWidth / _childSize.Width,
                        viewbox.ActualHeight / _childSize.Height);
                }

                AssertScaleValue();

                // Giới hạn khung nhìn của nội dung, chỉ cho nhìn thấy khi còn ở trong root.
                //viewbox.Clip = new RectangleGeometry
                //{
                //    Rect = new Rect(new Point(0, 0), root.RenderSize)
                //};

                // Giới hạn khung nhìn chỉ cho nhìn trong vùng của root.
                root.Clip = new RectangleGeometry
                {
                    Rect = new Rect(new Point(0, 0), root.RenderSize)
                };
            }
        }

        /// <summary>
        /// Đặt biên độ cho các hàm easing
        /// </summary>
        /// <param name="amplitude"></param>
        private void SetBackEaseAmplitude(double amplitude)
        {
            restoreScaleXBackEase.Amplitude = restoreScaleYBackEase.Amplitude = amplitude;
            restoreTranslateXBackEase.Amplitude = restoreTranslateYBackEase.Amplitude = amplitude;
        }

        /// <summary>
        /// Tạm dừng tất cả các Storyboard.
        /// </summary>
        private void PauseAllStoryboard()
        {
            if (_flickStoryboardState == StoryboardState.Running)
            {
                flickStoryboard.Pause();
                _flickStoryboardState = StoryboardState.Paused;
            }
            if (_restoreScaleStoryboardState == StoryboardState.Running)
            {
                restoreScaleStoryboard.Pause();
                _restoreScaleStoryboardState = StoryboardState.Paused;
            }
            if (_restoreTranslateStoryboardState == StoryboardState.Running)
            {
                restoreTranslateStoryboard.Pause();
                _restoreTranslateStoryboardState = StoryboardState.Paused;
            }
        }

        /// <summary>
        /// Kiểm tra tính thỏa mãn của các giá trị scale.
        /// </summary>
        private void AssertScaleValue()
        {
            if (_isInitialized)
            {
                if (!(_minScale <= _preferredMinScale &&
                      _preferredMinScale <= _preferredMaxScale &&
                      _preferredMaxScale <= _maxScale))
                {
                    throw new ArgumentException();
                }
            }
        }

        /// <summary>
        /// Lấy độ dịch chuyển của ảnh khi scale.
        /// </summary>
        /// <param name="newScale"></param>
        /// <returns></returns>
        private Point CalculateScaleTransition(double newScale)
        {
            double currentScale = _transform.ScaleX;

            Point centerTranslateDelta = CalculateTransition(newScale);
            Point totalTranslateDelta = centerTranslateDelta;

            // Vị trí của ảnh sau khi scale và dịch chuyển tâm theo viewbox và kích thước theo root
            Rect newImagePosition = GetImagePosition(
                new Point(
                    _transform.TranslateX + centerTranslateDelta.X * _childSizeRate.X,
                    _transform.TranslateY + centerTranslateDelta.Y * _childSizeRate.Y),
                newScale);

            // Cạnh trái
            if (newImagePosition.Left > 0)
            {
                totalTranslateDelta.X += -newImagePosition.Left;
            }
            // Cạnh phải
            else
            {
                // viewbox nhỏ hơn root, tính theo tọa độ viewbox
                if (viewbox.ActualWidth <= root.ActualWidth && newImagePosition.Right < viewbox.ActualWidth)
                {
                    totalTranslateDelta.X += (viewbox.ActualWidth - newImagePosition.Right);
                }
                // không thì tính theo tọa độ root
                else if (viewbox.ActualWidth > root.ActualWidth && newImagePosition.Right < root.ActualWidth)
                {
                    totalTranslateDelta.X += (root.ActualWidth - newImagePosition.Right);
                }
            }

            // Cạnh trên
            if (newImagePosition.Top > 0)
            {
                totalTranslateDelta.Y += -newImagePosition.Top;
            }
            // Cạnh dưới
            else
            {
                // viewbox nhỏ hơn root, tính theo tọa độ viewbox
                if (viewbox.ActualHeight <= root.ActualHeight && newImagePosition.Bottom < viewbox.ActualHeight)
                {
                    totalTranslateDelta.Y += (viewbox.ActualHeight - newImagePosition.Bottom);
                }
                else if (viewbox.ActualHeight > root.ActualHeight && newImagePosition.Bottom < root.ActualHeight)
                {
                    totalTranslateDelta.Y += (root.ActualHeight - newImagePosition.Bottom);
                }
            }

            return totalTranslateDelta;
        }
        #endregion
        #endregion

        #region //----------------------------- Class Public Methods --------------------------------------//
        public void Zoom(Point pinchCenter, double scale, Duration duration)
        {
            if (scale < _minScale)
            {
                throw new ArgumentOutOfRangeException("scale", "Mức độ phóng phải lớn hơn giá trị tối thiểu cho phép");
            }

            if (scale > _maxScale)
            {
                throw new ArgumentOutOfRangeException("scale", "Mức độ phóng phải nhỏ hơn giá trị tối đa cho phép");
            }

            Rect imagePosition = GetImageRootPosition();

            pinchCenter.X *= _transform.ScaleX;
            pinchCenter.Y *= _transform.ScaleY;

            if (!imagePosition.Contains(pinchCenter))
            {
                // TODO: Không hiểu tại sao ??
                //throw new ArgumentOutOfRangeException("pinchCenter", "Tâm phải nằm trong Rect chứa image");
            }

            _pinchCenter = pinchCenter;
            RestoreScale(scale, duration);
        }

        /// <summary>
        /// Zoom Child tại vị trí pinchCenter với mức độ phóng là scale.
        /// </summary>
        /// <param name="pinchCenter">Tâm zoom.</param>
        /// <param name="scale">Độ phóng.</param>
        public void Zoom(Point pinchCenter, double scale)
        {
            if (scale < _minScale)
            {
                throw new ArgumentOutOfRangeException("scale", "Mức độ phóng phải lớn hơn giá trị tối thiểu cho phép");
            }

            if (scale > _maxScale)
            {
                throw new ArgumentOutOfRangeException("scale", "Mức độ phóng phải nhỏ hơn giá trị tối đa cho phép");
            }

            Rect imagePosition = GetImageRootPosition();

            pinchCenter.X *= _transform.ScaleX;
            pinchCenter.Y *= _transform.ScaleY;

            if (!imagePosition.Contains(pinchCenter))
            {
                // TODO: Không hiểu tại sao ??
                //throw new ArgumentOutOfRangeException("pinchCenter", "Tâm phải nằm trong Rect chứa image");
            }

            _pinchCenter = pinchCenter;
            RestoreScale(scale);
        }

        /// <summary>
        /// Phóng ảnh tới độ phóng scale và tâm phóng là tâm cũ.
        /// </summary>
        /// <param name="scale"></param>
        public void Zoom(double scale)
        {
            if (scale < _minScale)
            {
                throw new ArgumentOutOfRangeException("scale", "Mức độ phóng phải lớn hơn giá trị tối thiểu cho phép");
            }

            if (scale > _maxScale)
            {
                throw new ArgumentOutOfRangeException("scale", "Mức độ phóng phải nhỏ hơn giá trị tối đa cho phép");
            }

            RestoreScale(scale);
        }
        #endregion
    }
}
